import platform
import logging
import socket
import util.json as json

import io.ioloop
import tx.comm_id

import atom
import guid
import scheduler
import verset

log = logging.getLogger('tx')

# -----------------------------------------------------------------------------
class TransportConn(io.ioloop.SockHandle):
    def __init__(self, transport, sock):
        io.ioloop.SockHandle.__init__(self, sock)
        self.transport_ = transport
        self.transport_.set_tx_handle(self)

    def recv_frame(self, msg):
        try:
            if msg[0] == 'h':
                self.handshake_req(msg)
            elif msg[0] == 'd':
                self.data_req(msg)
            elif msg[0] == 'H':
                self.handshake_recv(msg)
            elif msg[0] == 'D':
                self.data_recv(msg)
	    elif msg[0] == 'a':
		self.ack_recv(msg)
	    elif msg[0] == 'f':
		self.deactivate_iter(msg)
            elif msg[0] == 'i':
                # XXX/bowei - this is an informational message
                # (e.g. signals about the lower layer, tba)
                pass
        except ValueError:
            log.error('Error unpacking message from transport: "%s"' % msg)
        except IndexError:
            log.error('Error unpacking message from transport: "%s"' % msg)

    def handshake_req(self, msg):
        _, c_id = msg.split(' ', 1)
        c_id = tx.comm_id.from_str(c_id)
        log.debug('hs req from %s' % c_id)
        hs = self.transport_.handshake_req(c_id)
        self.send_frame('h %s %s' % (c_id, hs))

    def data_req(self, msg):
        _, c_id  = msg.split(' ', 1)
        c_id = tx.comm_id.from_str(c_id)
        log.debug('data req %s' % c_id)
        # Note: the scheduler in transport may trigger a send
        self.transport_.data_req(c_id)
        
    def handshake_recv(self, msg):
        _, c_id, hs = msg.split(' ', 2)
        c_id = tx.comm_id.from_str(c_id)
        log.debug('hs recv from %s: %s' % (c_id, hs))
        hs = json.read(hs)

        # XXX/bowei -- move this code to Transport
        self.transport_.sync_.get_peer_vs(c_id.peer_id).from_compact_encoding(hs)

        # XXX/bowei -- AAA sched update with new information (reset iterators)
        # self.transport_.reset_iterator(peer_id)
        # XXX/bowei -- need a DB commit here
        
    def data_recv(self, msg):
        _, c_id, data = msg.split(' ', 2)
        c_id = tx.comm_id.from_str(c_id)
        self.transport_.recv_data(c_id, data)

    def ack_recv(self, msg):
	raise NotImplementedError()

    def deactivate_iter(self, msg):
	_, c_id = msg.split(' ', 1)
        c_id = tx.comm_id.from_str(c_id)
        # XXX/bowei -- AAAA deactivate sched?

    def close(self):
	self.transport_.unset_tx_handle(self)
	io.ioloop.SockHandle.close(self)

# -----------------------------------------------------------------------------
class Transport:
    def __init__(self, sync, sched_module, ap):
        self.sync_ = sync
        self.ap_ = ap
        self.tx_h_ = None

        if sched_module:
            log.info('using %s for the scheduler')
            modname = sched_module.split('.')[-1]
            sched_mod = __import__(sched_module, globals(), locals(),
                                   [modname], 0)
            log.error(dir(sched_mod))
            self.scheduler_ = sched_mod.Scheduler(sync, self.send_data)
        else:
            log.info('scheduler unconfigured, using basic scheduler ()')
            self.scheduler_ = scheduler.BasicScheduler(sync, self.send_data)
        
    def connect(self):
        log.info('trying to connect to transport %s:%s' %
                 (self.ap_[0], self.ap_[1]))
        try:
            if ':' in self.ap_[0]:
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.connect(self.ap_)
            else:
                sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
                sock.connect(self.ap_[0])
        except socket.error, e:
            if platform.system() == 'Windows':
                log.warning('Connection to tx failed: %s' % e)
                return
            else:
                # XXX/bowei -- should retry in deployment scenario
                log.warning('Connection to tx failed: %s, failover to stdin' % e)
                sock = io.ioloop.StdinSockAdapter()
        tc = TransportConn(self, sock)
        io.ioloop.master_loop.add_handle(tc)

    def set_tx_handle(self, h):
        if self.tx_h_ != None:
            log.error('More than one transport is connected')
            return
        self.tx_h_ = h
        log.info('-- transport connected, %s' % h)

    def unset_tx_handle(self, h):
	assert self.tx_h_ == h
        log.info('-- transport disconnected, %s' % h)
	self.tx_h_ = None

    #
    # methods called from the TransportConn
    #
    def handshake_req(self, c_id):
        # XXX/bowei -- this should do some kind of callout to be smarter
        return json.write(self.sync_.get_own_vs().get_compact_encoding())

    def data_req(self, c_id):
        self.scheduler_.req_data(c_id)

    def update_scheduler(self, pv):
        self.scheduler_.update(pv)

    def check_reqs(self):
        self.scheduler_.check_reqs()

    def recv_data(self, c_id, data):
        log.debug('--- recv_data(%s)' % (c_id))
        try:
            data = json.read(data)
        except json.ReadException:
            log.error('Invalid json for atom, ignoring: %s' % data)
            return

        typename = data['type']
        if typename == 'neon_nop':
            g = guid.from_raw(data['neon_guid'])
            log.debug('recv_data: nop for %s' % g)
            self.sync_.get_own_vs().set_ver(g.id(), g.ver(), 1)
            self.sync_.get_peer_vs(c_id.peer_id).set_ver(g.id(), g.ver(), 1)
            return

        a = atom.Atom(self.sync_.atom_store().get_type(typename))
        a.from_raw(data)
        id, ver = a.guid.as_tuple()

        if self.sync_.get_own_vs().get_ver(id, ver):
            log.warn('Duplicate data recv %s:%s, ignoring' % (id, ver))
            return

        log.debug('--- recv_data(%s) - got atom' % a)
        self.sync_.get_own_vs().set_ver(id, ver, 1)
        self.sync_.get_peer_vs(c_id.peer_id).set_ver(id, ver, 1)
        self.update_scheduler((id, ver))
        self.sync_.put_atom(a)

        # XXX/bowei -- DB commit?
        # XXX/bowei -- update scheduler? (potentially enqueue on
        # higher priority queues?)

    def send_data(self, c_id, msg):
        """This method is attached to the scheduler to be used as the
        send_fcn."""
        log.debug('--- send_data(%s) %s' % (c_id, msg))
        p, v = msg
	g = guid.Guid(p,v)
        a = self.sync_.get_atom(g)
        if a:
            a = a.to_raw()
        else:
            a = {'type' : 'neon_nop', 'neon_guid' : str(g)}
	# XXX/bowei -- need protocol to send on
        self.tx_h_.send_frame('d %s %s' % 
		              (c_id, json.write(a)))
        self.sync_.get_peer_vs(c_id.peer_id).set_ver(p, v, 1)
        self.sync_.db_.commit()
